001 /*
002 * Copyright 2005 Stephen J. McConnell.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.tools.impl;
020
021 import java.io.File;
022 import java.net.URI;
023 import java.net.URL;
024 import java.util.Vector;
025
026 import net.dpml.library.Builder;
027 import net.dpml.library.Library;
028 import net.dpml.library.Resource;
029
030 import net.dpml.tools.Context;
031 import net.dpml.tools.info.BuilderDirective;
032 import net.dpml.tools.info.BuilderDirectiveHelper;
033
034 import net.dpml.transit.Artifact;
035 import net.dpml.transit.model.TransitModel;
036 import net.dpml.transit.tools.MainTask;
037
038 import net.dpml.util.Logger;
039
040 import org.apache.tools.ant.Project;
041 import org.apache.tools.ant.ProjectHelper;
042 import org.apache.tools.ant.BuildException;
043 import org.apache.tools.ant.BuildLogger;
044 import org.apache.tools.ant.DefaultLogger;
045 import org.apache.tools.ant.input.DefaultInputHandler;
046 import org.apache.tools.ant.DemuxInputStream;
047
048 /**
049 * The StandardBuilder is a plugin established by the Tools build controller
050 * used for the building of a project based on the Ant build system in conjunction
051 * with Transit plugin management services.
052 *
053 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
054 * @version 1.1.0
055 */
056 public class StandardBuilder implements Builder
057 {
058 // ------------------------------------------------------------------------
059 // static
060 // ------------------------------------------------------------------------
061
062 /**
063 * The default template uri path.
064 */
065 public static final String DEFAULT_TEMPLATE_URN = "local:template:dpml/tools/standard";
066
067 /**
068 * The builder configuration.
069 */
070 public static final BuilderDirective CONFIGURATION = loadConfiguration();
071
072 private static BuilderDirective loadConfiguration()
073 {
074 try
075 {
076 return BuilderDirectiveHelper.build();
077 }
078 catch( Throwable e )
079 {
080 final String error =
081 "Internal error while attempting to establish the builder configuration.";
082 BuilderError be = new BuilderError( error, e );
083 be.printStackTrace();
084 return null;
085 }
086 }
087
088 // ------------------------------------------------------------------------
089 // state
090 // ------------------------------------------------------------------------
091
092 private Logger m_logger;
093 private TransitModel m_model;
094 private Library m_library;
095 private boolean m_verbose;
096 private Throwable m_result;
097
098 // ------------------------------------------------------------------------
099 // constructors
100 // ------------------------------------------------------------------------
101
102 /**
103 * Creation of a new standard builder.
104 *
105 * @param logger assigned logging channel
106 * @param library the library
107 */
108 public StandardBuilder( Logger logger, Library library )
109 {
110 this( logger, library, false );
111 }
112
113 /**
114 * Creation of a new standard builder.
115 *
116 * @param logger assigned logging channel
117 * @param library the library
118 * @param verbose verbose execution flag
119 */
120 public StandardBuilder( Logger logger, Library library, boolean verbose )
121 {
122 m_logger = logger;
123 m_verbose = verbose;
124 m_library = library;
125
126 Thread.currentThread().setContextClassLoader( Builder.class.getClassLoader() );
127 }
128
129 // ------------------------------------------------------------------------
130 // Builder
131 // ------------------------------------------------------------------------
132
133 /**
134 * Build the project defined by the supplied resource.
135 * @param resource the project definition
136 * @param targets an array of build target names
137 * @return the build success status
138 */
139 public boolean build( Resource resource, String[] targets )
140 {
141 Project project = createProject( resource );
142 File template = getTemplateFile( resource );
143 return build( resource, project, template, targets );
144 }
145
146 /**
147 * Return the template for the resource.
148 * @param resource the project definition
149 * @return the template
150 */
151 public File getTemplateFile( Resource resource )
152 {
153 try
154 {
155 String systemOverride = System.getProperty( "project.template" );
156 String override = resource.getProperty( "project.template", systemOverride );
157 if( null != override )
158 {
159 File template = getTemplateFile( override );
160 return template;
161 }
162
163 File basedir = resource.getBaseDir();
164 String buildfile = resource.getProperty( "project.buildfile" );
165 String defaultBuildfile = resource.getProperty( "project.standard.buildfile", "build.xml" );
166 if( null != buildfile )
167 {
168 // there is an explicit 'project.buildfile' declaration in which case
169 // we check for existance and fail if it does not exist
170
171 File file = new File( basedir, buildfile );
172 if( file.exists() )
173 {
174 return file;
175 }
176 else
177 {
178 final String error =
179 "Resource buildfile ["
180 + file
181 + "] does not exist.";
182 throw new BuildException( error );
183 }
184 }
185 else if( null != defaultBuildfile )
186 {
187 // check if a buildfile of the default name exists in the project's
188 // basedir - and if so - use it to build the project
189
190 File file = new File( basedir, defaultBuildfile );
191 if( file.exists() )
192 {
193 return file;
194 }
195 }
196
197 // otherwise we build using either an explicit or default template
198 // resolved via a uri (typically a template stored in prefs)
199
200 String defaultTemplateSpec =
201 resource.getProperty( "project.standard.template", DEFAULT_TEMPLATE_URN );
202 String templateSpec = resource.getProperty( "project.template", defaultTemplateSpec );
203
204 if( null != templateSpec )
205 {
206 File template = getTemplateFile( templateSpec );
207 return template;
208 }
209 else
210 {
211 final String error =
212 "Resource template property 'project.template' is undefined.";
213 throw new BuildException( error );
214 }
215 }
216 catch( BuildException e )
217 {
218 throw e;
219 }
220 catch( Throwable e )
221 {
222 m_result = e;
223 final String error =
224 "Unexpected error while attempting to resolve project template."
225 + "\nResource path: "
226 + resource.getResourcePath();
227 throw new BuildException( error, e );
228 }
229 }
230
231 // ------------------------------------------------------------------------
232 // implementation
233 // ------------------------------------------------------------------------
234
235 boolean build( Resource resource, Project project, File template, String[] targets )
236 {
237 try
238 {
239 ProjectHelper helper = (ProjectHelper) project.getReference( "ant.projectHelper" );
240
241 if( null != template )
242 {
243 helper.parse( project, template );
244 }
245
246 Vector vector = new Vector();
247
248 if( targets.length == 0 )
249 {
250 if( null != project.getDefaultTarget() )
251 {
252 vector.addElement( project.getDefaultTarget() );
253 }
254 else
255 {
256 vector.addElement( CONFIGURATION.getDefaultPhase() );
257 }
258 }
259 else
260 {
261 for( int i=0; i<targets.length; i++ )
262 {
263 String target = targets[i];
264 vector.addElement( target );
265 }
266 }
267
268 if( vector.size() == 0 )
269 {
270 final String errorMessage =
271 "No targets requested and no default target declared.";
272 throw new BuildException( errorMessage );
273 }
274
275 project.executeTargets( vector );
276 return true;
277 }
278 catch( BuildException e )
279 {
280 m_result = e;
281 if( m_logger.isDebugEnabled() )
282 {
283 final String error =
284 "Build failure."
285 + "\nProject: " + resource.getResourcePath()
286 + "\nBasedir: " + resource.getBaseDir()
287 + "\nTemplate: " + template
288 + "\nLocation: " + e.getLocation();
289 Throwable cause = e.getCause();
290 m_logger.error( error, cause );
291 }
292 return false;
293 }
294 finally
295 {
296 project.fireBuildFinished( m_result );
297 }
298 }
299
300 private File getTemplateFile( String spec )
301 {
302 try
303 {
304 URI uri = new URI( spec );
305 if( Artifact.isRecognized( uri ) )
306 {
307 URL url = uri.toURL();
308 return (File) url.getContent( new Class[]{File.class} );
309 }
310 }
311 catch( Throwable e )
312 {
313 }
314 return new File( spec );
315 }
316
317 Project createProject( Resource resource )
318 {
319 try
320 {
321 Project project = newProject();
322 Context context = new DefaultContext( resource, project );
323 return project;
324 }
325 catch( Exception e )
326 {
327 final String error =
328 "Unable to establish build context."
329 + "\nProject: " + resource;
330 throw new BuildException( error, e );
331 }
332 }
333
334 private Project newProject()
335 {
336 Project project = new Project();
337 project.setSystemProperties();
338 project.setDefaultInputStream( System.in );
339 setupTransitComponentHelper( project );
340 project.setCoreLoader( getClass().getClassLoader() );
341 project.addBuildListener( createLogger() );
342 System.setIn( new DemuxInputStream( project ) );
343 project.setProjectReference( new DefaultInputHandler() );
344 ProjectHelper helper = ProjectHelper.getProjectHelper();
345 project.addReference( "ant.projectHelper", helper );
346 return project;
347 }
348
349 private void setupTransitComponentHelper( Project project )
350 {
351 try
352 {
353 MainTask task = new MainTask();
354 task.setProject( project );
355 task.init();
356 task.execute();
357 }
358 catch( BuildException e )
359 {
360 throw e;
361 }
362 catch( Exception e )
363 {
364 final String error =
365 "Setup failure.";
366 throw new BuildException( error, e );
367 }
368 }
369
370 private BuildLogger createLogger()
371 {
372 BuildLogger logger = new DefaultLogger();
373 if( m_verbose )
374 {
375 logger.setMessageOutputLevel( Project.MSG_VERBOSE );
376 }
377 else
378 {
379 logger.setMessageOutputLevel( Project.MSG_INFO );
380 }
381 logger.setOutputPrintStream( System.out );
382 logger.setErrorPrintStream( System.err );
383 return logger;
384 }
385
386 private Logger getLogger()
387 {
388 return m_logger;
389 }
390 }
391